<?php
/**
 * @category Goatherd
 * @package Goatherd
 *
 * @author Copyright (c) 2012 Maik Penz <maik@phpkuh.de>
 * @version $Id: DuckableTrait.php 87 2012-03-28 22:52:43Z maik@phpkuh.de $
 *
 * This file is part of Goatherd library.
 *
 * Goatherd library is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * Goatherd library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Goatherd library. If not, see <http://www.gnu.org/licenses/>.
 */
namespace Goatherd\Magic;

/**
 * Duck-typing enabling trait.
 *
 * @category Goatherd
 * @package Goatherd
 * @subpackage Magic
 */
trait DuckableTrait
{
    /**
     *
     * @var array
     */
    protected $_duckableObjectPool = array();

    /**
     * Attach duck.
     *
     * @param string $key
     * @param IDuck $object
     * @throws \InvalidArgumentException
     */
    public function attachDuck($key, IDuck $object)
    {
        if (!is_string($key)) {
            throw new \InvalidArgumentException('Keys for duckable trait must be strings.');
        }
        if (!ctype_lower($key)) {
            throw new \InvalidArgumentException('Keys for duckable trait must consist of lower case letters only. \'%s\' given.', $key);
        }

        $this->_duckableObjectPool[$key] = $object;

        return $this;
    }

    /**
     * Detach duck.
     *
     * @param string $key
     */
    public function detachDuck($key)
    {
        if (array_key_exists($key, $this->_duckableObjectPool)) {
            unset($this->_duckableObjectPool[$key]);
        }

        return $this;
    }

    /**
     *
     * @param string $key
     * @return IDuck
     */
    public function getDuck($key)
    {
        if (array_key_exists($key, $this->_duckableObjectPool)) {
            return $this->_duckableObjectPool[$key];
        }
    }

    /**
     * Duck-typing abstraction.
     *
     * Will try to call $this->_object->someMethod($args,..)
     * for $this->objectSomeMethod($args,..).
     *
     * Called object must instanciate IDuck.
     *
     * @param string $method
     * @param array $args
     */
    public function __call($method, $args)
    {
        if (!is_string($method) || !ctype_alpha($method)) {
            throw new \InvalidArgumentException('Method name must be "camelCased" and letters only (a-z).');
        }

        // get name for object handle
        $methodParts = preg_split('/(?!^)(?=[[:upper:]])/u', $method, 2);
        $objectName = $methodParts[0];

        // test for object
        $object = $this->getDuck($objectName);
        if (null === $object) {
            throw new \InvalidArgumentException(sprintf('No such duck \'%s\'', $objectName));
        }

        // test for method; fix to allow invocation
        $method = array_key_exists(1, $methodParts) ? lcfirst($methodParts[1]) : '__invoke';
        if (!method_exists($object, $method)) {
            throw new \BadMethodCallException(sprintf('No such method \'%s\' for \'%s\'.', $method, get_class($object)));
        }

        // execute command
        return call_user_func_array(array($object, $method), $args);
    }
}
